package com.boot.webflux.security.example2.config;

import com.boot.webflux.security.example2.config.authentication.CloudAuthenticationConverter;
import com.boot.webflux.security.example2.config.authentication.CloudAuthenticationManager;
import com.boot.webflux.security.example2.config.authentication.CloudContextRepository;
import com.boot.webflux.security.example2.config.authorization.CloudAuthorizationManager;
import com.boot.webflux.security.example2.handler.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Mono;

import java.util.Iterator;
import java.util.LinkedList;


@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity // 开启方法级的权限注解,设置后控制器层的方法前的@PreAuthorize("hasRole('admin')") 注解才能起效
public class CloudSecurityConfig {
    @Autowired
    private CloudAccessDeniedHandler cloudAccessDeniedHandler;
    @Autowired
    private CloudAuthenticationEntryPoint cloudAuthenticationEntryPoint;
    @Autowired
    private CloudAuthenticationFailureHandler cloudAuthenticationFailureHandler;
    @Autowired
    private CloudAuthenticationSuccessHandler cloudAuthenticationSuccessHandler;
    @Autowired
    private CloudLogoutSuccessHandler cloudLogoutSuccessHandler;
    @Autowired
    private CloudUserDetailsService cloudUserDetailsService;
    @Autowired
    private CloudAuthenticationConverter cloudAuthenticationConverter;
    @Autowired
    private CloudAuthenticationManager cloudAuthenticationManager;
    @Autowired
    private CloudContextRepository cloudContextRepository;
    @Autowired
    private CloudAuthorizationManager cloudAuthorizationManager;
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        SecurityWebFilterChain chain = http
                .csrf().disable()
                .authorizeExchange()
                .pathMatchers("/auth/login", "/auth/logout").permitAll()

//                .anyExchange().authenticated()
                .anyExchange().access(cloudAuthorizationManager)

                .and().exceptionHandling()
                .authenticationEntryPoint(cloudAuthenticationEntryPoint)
                .accessDeniedHandler(cloudAccessDeniedHandler)

                .and()
                .authenticationManager(cloudAuthenticationManager)
                .securityContextRepository(cloudContextRepository)


                .formLogin().loginPage("/auth/login")
                .authenticationFailureHandler(cloudAuthenticationFailureHandler)
                .authenticationSuccessHandler(cloudAuthenticationSuccessHandler)

                .and().logout().logoutUrl("/auth/logout")
                .logoutSuccessHandler(cloudLogoutSuccessHandler)

                .and()
                .build()
                ;
        Iterator<WebFilter> weIterable = chain.getWebFilters().toIterable().iterator();
        while(weIterable.hasNext()) {
            WebFilter f = weIterable.next();
            if(f instanceof AuthenticationWebFilter) {
                AuthenticationWebFilter webFilter = (AuthenticationWebFilter) f;
                webFilter.setServerAuthenticationConverter(cloudAuthenticationConverter);
            }
        }
        return chain;
    }


    @Bean
    ReactiveAuthenticationManager reactiveAuthenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(authentication -> {
            // 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error
            return Mono.empty();
        });
        // 必须放最后不然会优先使用用户名密码校验
        // 但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
        managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(cloudUserDetailsService));
        return new DelegatingReactiveAuthenticationManager(managers);
    }
}
